//===============================================================================
// Q4Tech Engineering Team
// Mobile Updater Application Block - November 2006
//===============================================================================
// Copyright  Q4Tech Informatica Argentina.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Threading;
using System.IO;

namespace Microsoft.ApplicationBlocks.Mobile.Updater.Downloaders
{
    public class HTTPDownloader : IDownloader
    {
        #region Events

        public event DownloadTaskProgressEventHandler DownloadProgress;
        public event DownloadTaskStartedEventHandler DownloadStarted;
        public event DownloadTaskCompletedEventHandler DownloadCompleted;
        public event DownloadTaskErrorEventHandler DownloadError;

        #endregion

        private UpdaterTask currentTask;
        private Queue<FileManifest> pendingFiles = new Queue<FileManifest>();
        private FileManifest currentFile;
        private bool completed;
        private WebRequest currentRequest;

        #region IDownloader Members

        public void Download(UpdaterTask task, TimeSpan maxWaitTime)
        {
            BeginDownload(task);
            DateTime dueDateTime = DateTime.Now + maxWaitTime;

            while (!completed && dueDateTime > DateTime.Now)
                Thread.Sleep(2000);

            if (!completed && DownloadError != null)
                DownloadError(this, new DownloadTaskErrorEventArgs(currentTask, new TimeoutException("The file download timeout has expired.")));
        }

        public void BeginDownload(UpdaterTask task)
        {
            currentTask = task;

            foreach (FileManifest file in task.Manifest.Files)
                pendingFiles.Enqueue(file);

            if (DownloadStarted != null)
                DownloadStarted(this, new TaskEventArgs(task));

            DownloadCallback(null);
        }

        public bool CancelDownload(UpdaterTask task)
        {
            if (currentRequest == null)
                return false;

            try
            {
                currentRequest.Abort();
            }
            catch
            {
            }

            return true;
        }

        private void DownloadCallback(IAsyncResult asyncResult)
        {
            if (asyncResult != null &&
                !asyncResult.IsCompleted)
                return;

            if (currentRequest != null)
            {
                if (!ProcessRequest(asyncResult))
                    return;
            }

            if (DownloadProgress != null)
                DownloadProgress(this, new DownloadTaskProgressEventArgs(10000, 0, currentTask.Manifest.Files.Count, currentTask.Manifest.Files.Count - pendingFiles.Count, currentTask));

            if (pendingFiles.Count == 0)
            {
                if (DownloadCompleted != null)
                    DownloadCompleted(this, new TaskEventArgs(currentTask));

                completed = true;
            }
            else
            {
                try
                {
                    currentFile = pendingFiles.Dequeue();

                    string fileUri = currentTask.Manifest.Files.Base + currentFile.Source;
                    currentRequest = WebRequest.Create(fileUri);
                    currentRequest.BeginGetResponse(new AsyncCallback(DownloadCallback), null);
                }
                catch (Exception ex)
                {
                    if (DownloadError != null)
                    {
                        DownloadError(this, new DownloadTaskErrorEventArgs(currentTask, ex));
                    }

                    completed = true;
                    return;
                }
            }
        }

        private bool ProcessRequest(IAsyncResult asyncResult)
        {
            try
            {
                WebResponse response = currentRequest.EndGetResponse(asyncResult);

                DirectoryInfo filesBase = new DirectoryInfo(currentTask.DownloadFilesBase);

                if (!filesBase.Exists)
                    filesBase.Create();

                string localFile = Path.Combine(filesBase.FullName, currentFile.Source);
                FileStream fileStream = File.Open(localFile, FileMode.Create, FileAccess.Write, FileShare.None);
                Stream responseStream = response.GetResponseStream();

                const int bufferSize = 4096;

                byte[] buffer = new byte[bufferSize];

                int bytesRead;

                do
                {
                    bytesRead = responseStream.Read(buffer, 0, bufferSize);

                    if (bytesRead > 0)
                        fileStream.Write(buffer, 0, bytesRead);

                } while (bytesRead > 0);

                fileStream.Close();

                responseStream.Close();

                currentRequest = null;
            }
            catch (Exception ex)
            {
                if (DownloadError != null)
                {
                    DownloadError(this, new DownloadTaskErrorEventArgs(currentTask, ex));
                }

                completed = true;
                return false;
            }

            return true;
        }

        #endregion
    }
}

